using System;
using System.Collections.Generic;
using System.Linq;


namespace _3DTicTacToe.Core
{
    class TicTacToeGame
    {
        public delegate void OnTurnDlg(Turn turn);

        public event OnTurnDlg OnTurn;
        public event EventHandler<GameOverEventArgs> OnGameOver;

        private readonly List<Turn> _gameTurns;
        private readonly int[,,] _tm;

        private static int MaxTurnCount
        {
            get
            {
                return 64;
            }
        }

        public Player PlayerOne { get; private set; }
        public Player PlayerTwo { get; private set; }

        /// <summary>
        /// True if the game is over
        /// </summary>
        public bool IsOver { get; private set; }

        /// <summary>
        /// Get the player who must make the next turn
        /// </summary>
        public Player NextPlayer { get; private set; }

        /// <summary>
        /// The count of turns already made
        /// </summary>
        public int CountTurns
        {
            get
            {
                return _gameTurns.Count;
            }
        }

        public IEnumerable<Turn> Turns
        {
            get { return _gameTurns.ToArray(); }
        }

        #region Methods

        public bool IsValidTurn(Turn turn)
        {
            var inBounds = turn.ToArray().All(n => n < 4);
            var freeField = !_gameTurns.Select(n => new {n.X1, n.X2, n.X3}).
                                 Any(n => n.X1 == turn.X1 && n.X2 == turn.X2 && n.X3 == turn.X3);

            return inBounds && freeField;
        }

        private void UpdateGameState()
        {
            if (CountTurns < 7)
                return;

            Func<int, int[], GameOverEventArgs> win = (i, coords) =>
                                                          {
                                                              Log.Add(
                                                                  "Game over, Player "+i+1+" wins: " +
                                                                  coords.Select(n => n.ToString()).Aggregate(
                                                                      (n, m) => n + " " + m), "TTTGame");

                                                              IsOver = true;
                                                              return (i == 1)
                                                                         ? new GameOverEventArgs(PlayerOne, PlayerTwo,
                                                                                                 coords)
                                                                         : new GameOverEventArgs(PlayerTwo, PlayerOne,
                                                                                                 coords);
                                                          };

            for (var i = 0; i < 4; i++)
            {
                for (var j = 0; j < 4; j++)
                {
                    // horizontal 1
                    var a = _tm[i, j, 0];
                    if (a != 0 && a == _tm[i, j, 1] && a == _tm[i, j, 2] && a == _tm[i, j, 3])
                        OnGameOver(this, win(a, new[] {i, j, 0, i, j, 1, i, j, 2, i, j, 3}));

                    // horizontal 2
                    var b = _tm[i, 0, j];
                    if (b != 0 && b == _tm[i, 1, j] && b == _tm[i, 2, j] && b == _tm[i, 3, j])
                        OnGameOver(this, win(b, new[] {i, 0, j, i, 1, j, i, 2, j, i, 3, j}));

                    // vertical
                    var c = _tm[0, i, j];
                    if (c != 0 && c == _tm[1, i, j] && c == _tm[2, i, j] && c == _tm[3, i, j])
                        OnGameOver(this, win(c, new[] {0, i, j, 1, i, j, 2, i, j, 3, i, j}));
                }

                // diagonal 1
                var d1 = _tm[0, 0, i];
                if (d1 != 0 && d1 == _tm[1, 1, i] && d1 == _tm[2, 2, i] && d1 == _tm[3, 3, i])
                    OnGameOver(this, win(d1, new[] {0, 0, i, 1, 1, i, 2, 2, i, 3, 3, i}));

                d1 = _tm[3, 0, i];
                if (d1 != 0 && d1 == _tm[2, 1, i] && d1 == _tm[1, 2, i] && d1 == _tm[0, 3, i])
                    OnGameOver(this, win(d1, new[] {0, 0, i, 1, 1, i, 2, 2, i, 0, 3, i}));

                //diagonal 3
                var d3 = _tm[i, 0, 0];
                if (d3 != 0 && d3 == _tm[i, 1, 1] && d3 == _tm[i, 2, 2] && d3 == _tm[i, 3, 3])
                    OnGameOver(this, win(d3, new[] {i, 0, 0, i, 1, 1, i, 2, 2, i, 3, 3}));

                d3 = _tm[i, 3, 0];
                if (d3 != 0 && d3 == _tm[i, 2, 1] && d3 == _tm[i, 1, 2] && d3 == _tm[i, 0, 3])
                    OnGameOver(this, win(d3, new[] {i, 2, 0, i, 1, 1, i, 0, 2, i, 0, 3}));

                //diagonal 4
                var d4 = _tm[0, i, 0];
                if (d4 != 0 && d4 == _tm[1, i, 1] && d4 == _tm[2, i, 2] && d4 == _tm[3, i, 3])
                    OnGameOver(this, win(d4, new[] {i, 0, 0, i, 1, 1, i, 2, 2, 3, i, 3}));

                d4 = _tm[3, i, 0];
                if (d4 != 0 && d4 == _tm[2, i, 1] && d4 == _tm[1, i, 2] && d4 == _tm[0, i, 3])
                    OnGameOver(this, win(d4, new[] {i, 2, 0, i, 1, 1, i, 0, 2, 0, i, 3}));
            }

            // diagonal 2
            var d2 = _tm[0, 0, 0];
            if (d2 != 0 && d2 == _tm[1, 1, 1] && d2 == _tm[2, 2, 2] && d2 == _tm[3, 3, 3])
                OnGameOver(this, win(d2, new[] {0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3}));

            d2 = _tm[3, 0, 0];
            if (d2 != 0 && d2 == _tm[2, 1, 1] && d2 == _tm[1, 2, 2] && d2 == _tm[0, 3, 3])
                OnGameOver(this, win(d2, new[] {0, 2, 2, 1, 1, 1, 2, 0, 0, 0, 3, 3}));

            d2 = _tm[0, 3, 0];
            if (d2 != 0 && d2 == _tm[1, 2, 1] && d2 == _tm[2, 1, 2] && d2 == _tm[3, 0, 3])
                OnGameOver(this, win(d2, new[] {0, 2, 0, 1, 1, 1, 2, 0, 2, 3, 0, 3}));

            d2 = _tm[3, 3, 0];
            if (d2 != 0 && d2 == _tm[2, 2, 1] && d2 == _tm[1, 1, 2] && d2 == _tm[0, 0, 3])
                OnGameOver(this, win(d2, new[] {2, 2, 0, 1, 1, 1, 0, 0, 2, 0, 0, 3}));

            if (CountTurns == MaxTurnCount)
            {
                IsOver = true;

                if (OnGameOver != null)
                    OnGameOver(this, new GameOverEventArgs());
            }
        }

        /// <summary>
        /// Make a turn
        /// </summary>
        /// <param name="turn">The turn</param>
        public void MakeTurn(Turn turn)
        {
            if (_gameTurns.Count != 0)
            {
                if (IsOver)
                    throw new InvalidOperationException();

                if (NextPlayer != turn.Player)
                    throw new InvalidOperationException();
            }

            if (!IsValidTurn(turn))
                throw new InvalidOperationException();

            Log.Add("Turn made by " + turn.Player + ": " + turn.X1 + " " + turn.X2 + " " + turn.X3, "TTTGame");

            _gameTurns.Add(turn);
            _tm[turn.X1, turn.X2, turn.X3] = (turn.Player == PlayerOne) ? 1 : 2;
            NextPlayer = turn.Player == PlayerOne ? PlayerTwo : PlayerOne;

            if (OnTurn != null)
                OnTurn(turn);

            UpdateGameState();
        }

        #endregion

        public TicTacToeGame(Player playerOne, Player playerTwo)
        {
            PlayerOne = playerOne;
            PlayerTwo = playerTwo;

            NextPlayer = PlayerOne;

            IsOver = false;

            _gameTurns = new List<Turn>();
            _tm = new int[4,4,4];
        }
    }
}